# !/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time : 2023/4/26 10:06 
# @Author : zxl
# @Email : zhangxl_njit@163.com
# @File : xz_plan.py
# @Version: open-planner 2.0
"""
文件说明：xz_plan算法

"""

import time
import json
from copy import *
from OpenPlanner.core.api.solver_interface import PlanningStrategy
from OpenPlanner.usr.xz_plan.input.reinit_instance import *


class XzPlan(PlanningStrategy):
    # 重写方法
    def do_planning(self):
        link_obj_set = self.link_array
        stream_obj_set = self.stream_array
        stream_instance_obj_set = self.instance_array
        intances = solve(stream_instance_obj_set=stream_instance_obj_set)
        export_report(intances, self.solution_txt)

def solve(**kwargs):
    # 解析kwargs
    if kwargs.get('link_obj_set') is not None:
        link_obj_set = kwargs.get('link_obj_set')
    if kwargs.get('stream_obj_set') is not None:
        stream_obj_set = kwargs.get('stream_obj_set')
    if kwargs.get('stream_instance_obj_set') is not None:
        stream_instance_obj_set = kwargs.get('stream_instance_obj_set')
    if kwargs.get('stream_instance_obj_set') is not None:
        node_obj_set = kwargs.get('node_obj_set')
    port_num = _get_port_num(stream_instance_obj_set)
    i = 0
    while i < port_num:
        degree, port = _pick_max_conflict_port(stream_instance_obj_set)
        if degree == 0:
            break
        else:
            # 最大冲突度接口消解
            stream_instance_obj_set = _conflict_resolve(port, stream_instance_obj_set)
            # 流分割
            sub_stream_instance_obj_set = _flow_slicing(stream_instance_obj_set, port)
            # 子子流窗口修正
            sub_stream_instance_obj_set = time_window_revise(sub_stream_instance_obj_set)
            # 子子流窗口同步到流实例数组中，待下一轮计算
            stream_instance_obj_set = _update_window(sub_stream_instance_obj_set, stream_instance_obj_set)
        i += 1

    stream_instance_obj_set = _get_slot(stream_instance_obj_set)
    return stream_instance_obj_set


def _get_port_num(stream_instance_obj_set):
    port_win_set = _get_port_win_set(stream_instance_obj_set)
    return len(port_win_set)


def _get_port_win_set(stream_instance_obj_set):
    # port_win_set的格式为:
    # [{
    #         "port"="1_2",   //节点id_输出端口号
    #         "win":
    #           {
    #                 1(流id)：
    #                   [
    #                        [0, 2],
    #                        [10,12],
    #                        [20,22]
    #                   ],
    #                  2:[
    #                        [0, 2],
    #                        [10,12],
    #                        [20,22]
    #                  ]
    #           }
    #         ,
    #         "conflict": 0
    # },{
    # }]
    port_win_set = []
    for stream_instance_obj_per_stream in stream_instance_obj_set:
        for stream_instance_obj in stream_instance_obj_per_stream:
            stream_id = stream_instance_obj.stream_id
            seq = stream_instance_obj.seq
            # 有不同路径
            path_set = stream_instance_obj.path_set
            for path in path_set:
                talker = path.talker
                _append_win_to_port_win_set(talker, stream_id, port_win_set)
                # listener=path.listener
                # _append_win_to_port_win_set(listener, stream_id, port_win_set)
                bridges = path.bridges
                for sw in bridges:
                    _append_win_to_port_win_set(sw, stream_id, port_win_set)
    return port_win_set


def _append_win_to_port_win_set(node, stream_id, port_win_set):
    node_win = node.win
    node_id = node.node_id
    node_outport = node.outport
    win_key = str(node_id) + "_" + str(node_outport)
    i = 0
    while i < len(port_win_set):
        port_item = port_win_set[i]
        if port_item["port"] == win_key:
            win_dict = port_item["win"] if "win" in port_item else {}
            #  win_list_of_stream为每个流对应的窗口数组
            win_set_of_stream = _format_win_list_to_set(win_dict[stream_id]) if stream_id in win_dict else set()
            win_set_of_stream.add(tuple(node_win))
            win_dict[stream_id] = _format_win_set_to_list(win_set_of_stream)
            port_item["win"] = win_dict
            # 冲突度初始化为0
            port_item["conflict"] = 0
            break
        i = i + 1
    # 如果之前没有该项，则新添加
    if i == len(port_win_set):
        new_port_item = {}
        new_port_item["port"] = win_key
        new_win_dict = {}
        new_win_dict[stream_id] = [node_win]
        new_port_item["win"] = new_win_dict
        new_port_item["conflict"] = 0
        port_win_set.append(new_port_item)


def _format_win_list_to_set(list):
    win_set = set()
    for item in list:
        win_set.add(tuple(item))
    return win_set


def _format_win_set_to_list(set):
    sort_set = sorted(set)
    win_list = [list(row) for row in sort_set]
    return win_list


def _pick_max_conflict_port(stream_instance_obj_set):
    port_win_set = _get_port_win_set(stream_instance_obj_set)
    # 计算每个端口的重复度，并选出最大的重复的和端口
    max_degree = 0
    max_port = ""
    for por_win_item in port_win_set:
        win_dict = por_win_item["win"]
        degree_of_port = _get_conflict_degree_of_port(win_dict)
        por_win_item["conflict"] = degree_of_port
        if max_degree < degree_of_port:
            max_degree = degree_of_port
            max_port = por_win_item["port"]
    return max_degree, max_port


def _get_conflict_degree_of_port(win_dict):
    degree = 0
    win_keys = list(win_dict.keys())
    for i in range(0, len(win_keys)):
        for j in range(i + 1, len(win_keys)):
            win_list_a = win_dict[win_keys[i]]
            win_list_b = win_dict[win_keys[j]]
            degree = degree + _get_conflict_degree_of_two_stream(win_list_a, win_list_b)
    return degree


def _get_conflict_degree_of_two_stream(win_list_a, win_list_b):
    degree = 0
    for i in range(0, len(win_list_a)):
        for j in range(0, len(win_list_b)):
            if _check_overlap([win_list_a[i], win_list_b[j]]):
                degree += 1
    return degree


# 检查两个窗口是否重叠，如重叠，返回true
def _check_overlap(check_list):
    a_list = sorted(check_list, key=lambda l: l[0])
    for i in range(0, len(a_list) - 1):
        if a_list[i + 1][0] <= a_list[i][1]:
            return True
    return False


# 冲突消解
def _conflict_resolve(port, stream_instance_obj_set):
    # 直接修改流实例集合stream_instance_obj_set
    # stream_instance_obj_set_copy = deepcopy(stream_instance_obj_set)
    port_win_set = _get_port_win_set(stream_instance_obj_set)
    id_port_list = port.split("_")
    node_id = id_port_list[0]
    port_id = id_port_list[1]
    port_win_item = _get_win_port_item_by_port(port_win_set, port)
    win_dict = port_win_item["win"]
    win_keys = list(win_dict.keys())
    # shed_slots为每个输出端口已分配时隙集合
    # 对于某个节点的某个端口而言，shed_slots是不重复的集合，即每个流实例分配的时隙不相同
    shed_slots = set()
    for i in range(len(win_keys)):
        stream_id = win_keys[i]
        win_list = win_dict[win_keys[i]]
        for s in range(len(win_list)):
            # 流的周期
            seq = s
            win_before = win_list[s]
            start_slot = win_before[0]
            end_slot = win_before[1]
            shed_slot = start_slot
            while shed_slot <= end_slot:
                if shed_slot not in shed_slots:
                    shed_slots.add(shed_slot)
                    break
                else:
                    shed_slot += 1
            if shed_slot > end_slot:
                # print("Error,no appropriate window! Schedule failed!!")
                raise RuntimeError('Schedule failed!!')
            new_win = [shed_slot, shed_slot]
            win_list[s] = new_win[:]
            # 将消解的窗口同步到流实例中，并给时隙赋值
            # stream_instance_obj_copy = stream_instance_obj_set[stream_id - 1][seq]
            stream_instance_obj = stream_instance_obj_set[stream_id][seq]
            # path_set = stream_instance_obj_copy.path_set
            path_set = stream_instance_obj.path_set
            for path in path_set:
                talker = path.talker
                if talker.node_id == int(node_id) and talker.outport == int(port_id):
                    talker.win = new_win[:]
                    talker.slot = shed_slot
                bridges = path.bridges
                for sw in bridges:
                    if sw.node_id == int(node_id) and sw.outport == int(port_id):
                        sw.win = new_win[:]
                        sw.outslot = shed_slot
    # return stream_instance_obj_set_copy
    return stream_instance_obj_set


def _get_win_port_item_by_port(port_win_set, port):
    for item in port_win_set:
        if item["port"] == port:
            return item


# 流分割
def _flow_slicing(resolved_stream_instance_obj_set, port):
    sub_stream_instance_obj_set = []
    # 原先的流实例不改变其中的值，增加新的子子流实例
    resolved_stream_instance_obj_set_copy = deepcopy(resolved_stream_instance_obj_set)
    id_port_list = port.split("_")
    node_id = int(id_port_list[0])
    port_id = int(id_port_list[1])
    port_win_set = _get_port_win_set(resolved_stream_instance_obj_set_copy)
    port_win_item = _get_win_port_item_by_port(port_win_set, port)
    win_dict = port_win_item["win"]
    win_keys = list(win_dict.keys())
    # 对经过该接口的流进行分割
    for i in range(len(win_keys)):
        stream_id = win_keys[i]
        win_list = win_dict[win_keys[i]]
        sub_stream_instance_obj_per_stream = []
        for s in range(len(win_list)):
            # 流的周期
            seq = s
            stream_instance_obj_copy = resolved_stream_instance_obj_set_copy[stream_id][seq]
            sub_stream_instance_obj = deepcopy(stream_instance_obj_copy)
            path_set = stream_instance_obj_copy.path_set
            # sub_path_set为子子流的path集合
            sub_path_set = []
            # talker_listener_couple_set为子子流中起点和终点键值对，避免子子流重复
            talker_listener_couple_set = set()
            for path in path_set:
                old_talker = path.talker
                old_listener = path.listener
                old_bridges = path.bridges
                if old_talker.node_id == node_id and old_talker.outport == port_id:
                    # 如果talker就是分割点，那么分割成两条子子流。
                    # 一条以原来talker为起点终点。无需调整窗口。
                    # 一条以后一个为起点，原来终点为终点。
                    sub_sw = old_bridges[0]
                    kw = (sub_sw.node_id, old_listener.node_id)
                    if kw not in talker_listener_couple_set:
                        talker_listener_couple_set.add(kw)
                        new_talker = Talker(sub_sw.node_id, sub_sw.win, sub_sw.outport, sub_sw.outslot)
                        new_bridges = deepcopy(old_bridges[1:])
                        new_listener = deepcopy(old_listener)
                        new_path = Path(new_talker, new_listener, new_bridges)
                        sub_path_set.append(new_path)
                else:
                    # flag第一条路径的结束标志
                    flag = False
                    new_bridges_front = []
                    new_bridges_behind = []
                    new_talker_front = deepcopy(old_talker)
                    new_listener_behind = deepcopy(old_listener)
                    # 如果中间交换节点为分割点，分成两条子子流。1，talker-sw(i);2 sw(i+1) - listener
                    for sw in old_bridges:
                        # 消解点之前是第一条路径
                        if not flag:
                            new_bridges_front.append(sw)
                        if sw.node_id == node_id and sw.outport == port_id:
                            # 如果遇到消解点则取反
                            flag = bool(1 - flag)
                            # 去掉最后一个加入的交换节点，将其作为listener
                            new_bridges_front.pop()
                            kw_front = (old_talker.node_id, sw.node_id)
                            if kw_front not in talker_listener_couple_set:
                                # 原先的交换节点的inslot为新的Listener的slot，inport为Listener的port
                                new_listener_front = Listener(sw.node_id, sw.win, sw.inport, sw.inslot)
                                new_path = Path(new_talker_front, new_listener_front, new_bridges_front)
                                sub_path_set.append(new_path)
                        # 消解点之后是第二条路径
                        if flag:
                            new_bridges_behind.append(sw)
                    # 如果后续交换节点数组长度为0，则无需分割
                    #     遍历结束后，将后一条第一个交换节点作为第二条路径的talker，然后去掉
                    # 当后面路径长度大于1（至少为2），否则只有1个节点，则无需添加子路径
                    if len(new_bridges_behind) > 1:
                        # 第二个为起点
                        sw_first_behind = new_bridges_behind[1]
                        kw_behind = (sw_first_behind.node_id, new_listener_behind.node_id)
                        if kw_behind not in talker_listener_couple_set:
                            # sw的outport为talker的outport，sw的outslot为talker的slot
                            new_talker_behind = Talker(sw_first_behind.node_id, sw_first_behind.win,
                                                       sw_first_behind.outport, sw_first_behind.outslot)
                            del (new_bridges_behind[0])
                            new_path = Path(new_talker_behind, new_listener_behind, new_bridges_behind)
                            sub_path_set.append(new_path)
            sub_stream_instance_obj.path_set = sub_path_set
            sub_stream_instance_obj_per_stream.append(sub_stream_instance_obj)
        sub_stream_instance_obj_set.append(sub_stream_instance_obj_per_stream)
    return sub_stream_instance_obj_set


# 同步子子流窗口到流实例中
def _update_window(sub_stream_instance_obj_set, stream_instance_obj_set):
    for sub_stream_instance_obj_per_stream in sub_stream_instance_obj_set:
        for sub_stream_instance_obj in sub_stream_instance_obj_per_stream:
            stream_id = sub_stream_instance_obj.stream_id
            seq = sub_stream_instance_obj.seq
            # str = "流id为%s，第%s周期" % (stream_id, (seq))
            # print(str)
            path_set = sub_stream_instance_obj.path_set
            old_stream_instance_obj = stream_instance_obj_set[stream_id][seq]
            old_path_set = old_stream_instance_obj.path_set
            for path in path_set:
                sub_talker = path.talker
                _update_win_by_node(sub_talker, old_path_set)
                sub_listener = path.listener
                _update_win_by_node(sub_listener, old_path_set)
                sub_bridges = path.bridges
                for sub_sw in sub_bridges:
                    _update_win_by_node(sub_sw, old_path_set)
    return stream_instance_obj_set


# fixme 同步方法欠妥，节点的窗口不仅和node_id相关，而且和节点的输出端口相关。但是单播情况下无bug。
def _update_win_by_node(tt_node, old_path_set):
    node_id = tt_node.node_id
    win = tt_node.win
    for old_path in old_path_set:
        talker = old_path.talker
        if node_id == talker.node_id:
            # print("talker 同步", talker.win, win)
            talker.win = win[:]
            break
        listener = old_path.listener
        if node_id == listener.node_id:
            # print("listener 同步", listener.win, win)
            listener.win = win[:]
            break
        bridges = old_path.bridges
        for old_sw in bridges:
            if node_id == old_sw.node_id:
                # print("sw 同步", old_sw.win, win)
                old_sw.win = win[:]
                break


def _get_slot(stream_instance_obj_set):
    for stream_instance_obj_per_stream in stream_instance_obj_set:
        for stream_instance_obj in stream_instance_obj_per_stream:
            path_set = stream_instance_obj.path_set
            for path in path_set:
                talker = path.talker
                # 在无冲突的情况下，取最早时隙
                talker.slot = talker.win[0]
                listener = path.listener
                listener.slot = listener.win[0]
                bridges = path.bridges
                # sw的inslot为前一个sw/talker的outslot,同样listener的inslot为最后一个sw的outslot
                sw_start = bridges[0]
                sw_start.inslot = talker.slot
                sw_start.outslot = sw_start.win[0]
                pre_sw = sw_start
                i = 1
                while i < len(bridges):
                    bridges[i].inslot = pre_sw.outslot
                    bridges[i].outslot = bridges[i].win[0]
                    pre_sw = bridges[i]
                    i += 1
    return stream_instance_obj_set


def export_report(intances, output):
    # 输出格式：“年月日_时分秒_<算法名称>.json”
    cur_time = time.strftime('%Y%m%d_%H%M%S', time.localtime(time.time()))
    output_file_name = output
    dir = output_file_name.split('/')[:-1]
    fn = output_file_name.split('/')[-1]
    # fn = cur_time + fn
    solution_txt = ''
    for item in dir:
        item = item + '/'
        solution_txt += item
    solution_txt += fn
    f = open(solution_txt, 'w')
    result_by_json = []
    stream_instance_obj_set = deepcopy(intances)
    for stream_instance_obj_per_stream in stream_instance_obj_set:
        stream_instance_dict_per_stream = []
        for stream_instance_obj in stream_instance_obj_per_stream:
            stream_instance_obj_dict = stream_instance_obj.__dict__
            path_set = stream_instance_obj.path_set
            # 重新拼接输出
            stream_instance_obj_dict['path_set'] = []
            for path in path_set:
                path_dict = {'path': {}}
                talker = path.talker
                path_dict['path']['talker'] = dict(node_id=talker.node_id, outport=talker.outport, slot=talker.slot)
                bridges = path.bridges
                path_dict['path']['bridges'] = []
                for sw in bridges:
                    sw_dict = {'bridge': {}}
                    sw_dict['bridge'] = dict(node_id=sw.node_id, inport=sw.inport, inslot=sw.inslot,
                                             outport=sw.outport, outslot=sw.outslot)
                    path_dict['path']['bridges'].append(sw_dict)
                listener = path.listener
                path_dict['path']['listener'] = dict(node_id=listener.node_id, inport=listener.inport,
                                                     slot=listener.slot)
                stream_instance_obj_dict['path_set'].append(path_dict)
            stream_instance_dict_per_stream.append(stream_instance_obj_dict)
        result_by_json.append(stream_instance_dict_per_stream)
    result = json.dumps(result_by_json, indent=4, ensure_ascii=False)
    f.write(result)
    f.close()
